package com.dyna.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dyna.constants.ModuleErrorCodeConstants;
import com.dyna.core.exception.CustomException;
import com.dyna.core.utils.CollUtil;
import com.dyna.core.utils.MapUtil;
import com.dyna.enums.CommonStatusEnum;
import com.dyna.enums.MenuTypeEnum;
import com.dyna.foundation.domain.entity.SysMenu;
import com.dyna.foundation.domain.form.SysMenuSaveForm;
import com.dyna.foundation.domain.req.SysMenuListReq;
import com.dyna.foundation.domain.resp.SysMenuResp;
import com.dyna.mapper.SysMenuMapper;
import com.dyna.service.SysMenuService;
import com.dyna.service.SysPermissionService;
import com.google.common.annotations.VisibleForTesting;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

import static com.dyna.foundation.domain.entity.SysMenu.ID_ROOT;

/**
* @author zhoucaiwang
*/
@Service
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements SysMenuService {

    @Lazy
    @Resource
    private SysPermissionService sysPermissionService;

    @Override
    public Long createMenu(SysMenuSaveForm createReqVO) {
        // 校验父菜单存在
        validateParentMenu(createReqVO.getParentId(), null);
        // 校验菜单（自己）
        validateMenuName(createReqVO.getParentId(), createReqVO.getName(), null);
        validateMenuComponentName(createReqVO.getComponentName(), null);

        // 插入数据库
        SysMenu menu = BeanUtil.toBean(createReqVO, SysMenu.class);
        initMenuProperty(menu);
        baseMapper.insert(menu);
        // 返回
        return menu.getId();
    }

    /**
     * 初始化菜单的通用属性。
     * <p>
     * 例如说，只有目录或者菜单类型的菜单，才设置 icon
     *
     * @param menu 菜单
     */
    private void initMenuProperty(SysMenu menu) {
        // 菜单为按钮类型时，无需 component、icon、path 属性，进行置空
        if (MenuTypeEnum.BUTTON.getValue().equals(menu.getType())) {
            menu.setComponent("");
            menu.setComponentName("");
            menu.setIcon("");
            menu.setPath("");
        }
    }

    /**
     * 校验父菜单是否合法
     * <p>
     * 1. 不能设置自己为父菜单
     * 2. 父菜单不存在
     * 3. 父菜单必须是 {@link MenuTypeEnum#MENU} 菜单类型
     *
     * @param parentId 父菜单编号
     * @param childId  当前菜单编号
     */
    @VisibleForTesting
    void validateParentMenu(Long parentId, Long childId) {
        if (parentId == null || ID_ROOT.equals(parentId)) {
            return;
        }
        // 不能设置自己为父菜单
        if (parentId.equals(childId)) {
            throw new CustomException(ModuleErrorCodeConstants.MENU_PARENT_ERROR.getMsg());
        }
        SysMenu menu = baseMapper.selectById(parentId);
        // 父菜单不存在
        if (menu == null) {
            throw new CustomException(ModuleErrorCodeConstants.MENU_PARENT_NOT_EXISTS.getMsg());
        }
        // 父菜单必须是目录或者菜单类型
        if (!MenuTypeEnum.DIR.getValue().equals(menu.getType())
                && !MenuTypeEnum.MENU.getValue().equals(menu.getType())) {
            throw new CustomException(ModuleErrorCodeConstants.MENU_PARENT_NOT_DIR_OR_MENU.getMsg());
        }
    }

    /**
     * 校验菜单是否合法
     * <p>
     * 1. 校验相同父菜单编号下，是否存在相同的菜单名
     *
     * @param name     菜单名字
     * @param parentId 父菜单编号
     * @param id       菜单编号
     */
    @VisibleForTesting
    void validateMenuName(Long parentId, String name, Long id) {
        SysMenu menu = baseMapper.selectByParentIdAndName(parentId, name);
        if (menu == null) {
            return;
        }
        // 如果 id 为空，说明不用比较是否为相同 id 的菜单
        if (id == null) {
            throw new CustomException(ModuleErrorCodeConstants.MENU_NAME_DUPLICATE.getMsg());
        }
        if (!menu.getId().equals(id)) {
            throw new CustomException(ModuleErrorCodeConstants.MENU_NAME_DUPLICATE.getMsg());
        }
    }

    /**
     * 校验菜单组件名是否合法
     *
     * @param componentName 组件名
     * @param id            菜单编号
     */
    @VisibleForTesting
    void validateMenuComponentName(String componentName, Long id) {
        if (StrUtil.isBlank(componentName)) {
            return;
        }
        SysMenu menu = baseMapper.selectByComponentName(componentName);
        if (menu == null) {
            return;
        }
        // 如果 id 为空，说明不用比较是否为相同 id 的菜单
        if (id == null) {
            return;
        }
        if (!menu.getId().equals(id)) {
            throw new CustomException(ModuleErrorCodeConstants.MENU_COMPONENT_NAME_DUPLICATE.getMsg());
        }
    }

    @Override
    public void updateMenu(SysMenuSaveForm updateReqVO) {
        // 校验更新的菜单是否存在
        if (baseMapper.selectById(updateReqVO.getId()) == null) {
            throw new CustomException(ModuleErrorCodeConstants.MENU_NOT_EXISTS.getMsg());
        }
        // 校验父菜单存在
        validateParentMenu(updateReqVO.getParentId(), updateReqVO.getId());
        // 校验菜单（自己）
        validateMenuName(updateReqVO.getParentId(), updateReqVO.getName(), updateReqVO.getId());
        validateMenuComponentName(updateReqVO.getComponentName(), updateReqVO.getId());

        // 更新到数据库
        SysMenu updateObj = BeanUtil.toBean(updateReqVO, SysMenu.class);
        initMenuProperty(updateObj);
        baseMapper.updateById(updateObj);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteMenu(Long id) {
        // 校验是否还有子菜单
        if (baseMapper.selectCountByParentId(id) > 0) {
            throw new CustomException(ModuleErrorCodeConstants.MENU_EXISTS_CHILDREN.getMsg());
        }
        // 校验删除的菜单是否存在
        if (baseMapper.selectById(id) == null) {
            throw new CustomException(ModuleErrorCodeConstants.MENU_NOT_EXISTS.getMsg());
        }
        // 标记删除
        baseMapper.deleteById(id);
        // 删除授予给角色的权限
        sysPermissionService.processMenuDeleted(id);
    }

    /**
     * 递归生成菜单列表
     *
     * @param parentId 父级ID
     * @param menuList 菜单列表
     * @return 菜单列表
     */
    private List<SysMenuResp> buildMenuTree(Long parentId, List<SysMenu> menuList) {
        return CollectionUtil.emptyIfNull(menuList)
                .stream()
                .filter(menu -> menu.getParentId().equals(parentId))
                .map(entity -> {
                    SysMenuResp menuResp = BeanUtil.copyProperties(entity, SysMenuResp.class);
                    List<SysMenuResp> children = buildMenuTree(entity.getId(), menuList);
                    menuResp.setChildren(children);
                    return menuResp;
                }).collect(Collectors.toList());
    }

    @Override
    public List<SysMenu> getSysMenuListByRoleIds(List<Long> roleList) {
        if (CollUtil.isEmpty(roleList)) {
            return Collections.emptyList();
        }
        List<SysMenu> menuList = baseMapper.querySysMenuByRoleIds(roleList);
        return menuList;
    }

    @Override
    public List<SysMenu> getMenuList() {
        return baseMapper.selectList();
    }

    @Override
    public List<SysMenu> getMenuList(SysMenuListReq reqVO) {
        return baseMapper.selectList(reqVO);
    }

    @Override
    public List<SysMenu> filterDisableMenus(List<SysMenu> menuList) {
        if (cn.hutool.core.collection.CollUtil.isEmpty(menuList)){
            return Collections.emptyList();
        }
        Map<Long, SysMenu> menuMap = MapUtil.convertMap(menuList, SysMenu::getId);

        // 遍历 menu 菜单，查找不是禁用的菜单，添加到 enabledMenus 结果
        List<SysMenu> enabledMenus = new ArrayList<>();
        Set<Long> disabledMenuCache = new HashSet<>(); // 存下递归搜索过被禁用的菜单，防止重复的搜索
        for (SysMenu menu : menuList) {
            if (isMenuDisabled(menu, menuMap, disabledMenuCache)) {
                continue;
            }
            enabledMenus.add(menu);
        }
        return enabledMenus;
    }

    private boolean isMenuDisabled(SysMenu node, Map<Long, SysMenu> menuMap, Set<Long> disabledMenuCache) {
        // 如果已经判定是禁用的节点，直接结束
        if (disabledMenuCache.contains(node.getId())) {
            return true;
        }

        // 1. 先判断自身是否禁用
        if (CommonStatusEnum.isDisable(node.getStatus())) {
            disabledMenuCache.add(node.getId());
            return true;
        }

        // 2. 遍历到 parentId 为根节点，则无需判断
        Long parentId = node.getParentId();
        if (ObjUtil.equal(parentId, ID_ROOT)) {
            return false;
        }

        // 3. 继续遍历 parent 节点
        SysMenu parent = menuMap.get(parentId);
        if (parent == null || isMenuDisabled(parent, menuMap, disabledMenuCache)) {
            disabledMenuCache.add(node.getId());
            return true;
        }
        return false;
    }

    @Override
    public SysMenu getMenu(Long id) {
        return baseMapper.selectById(id);
    }
}




